import pygame
from pygame import Rect
from pygame.math import Vector2
from .gl_tools import *
V2 = Vector2


pygame.init()
font1 = pygame.font.SysFont("Arial", 24)


def clamp(x, a, b):
    assert a <= b
    return min(b, max(a, x))

def lerp(p1, p2, t):
    t = clamp(t, 0, 1)
    return p1 * (1 - t) + p2 * t

def mid(a, b):
    return (a + b) / 2

def id(x):
    return x


def X(p, screen):
    pygame.draw.line(screen, (255, 0, 0), p - V2(10, 0), p + V2(10, 0))
    pygame.draw.line(screen, (255, 0, 0), p - V2(0, 10), p + V2(0, 10))


def change(obj):
    def f(cls):
        d = cls.__dict__
        d = {k: d[k] for k in d if not k.startswith("__")}
        for k, v in d.items():
            if callable(v):
                setattr(obj, k, partial(v, obj))
            else:
                setattr(obj, k, v)
        obj.super = d
        return obj
    return f

import builtins
builtins.__dict__["change"] = change

class Control:
    pass

class Barv(Control):  # 使用数学坐标
    def __init__(self, p, k, color=(255, 255, 255), anchor=V2(0, -1)):
        self.k = k
        self.w = 0.2
        self.h = 0.8
        self.p = p
        self.color = color
        self.anchor = anchor

        self.draw_callback = self.draw
        self.down_callback = self.down
        self.motion_end_callback = self.motion_end
        self.motion_callback = self.motion
        self.change_callback = None

    def get_rect(self, window):
        os = window.os
        p = self.p_pixel(window)
        h = os(self.h)
        w = os(self.w)
        return Rect(p.x, p.y , w, h)

    def p_pixel(self, window):
        anchor2 = (self.anchor - V2(-1, 1)) / 2
        return window.o(self.p - V2(self.w * anchor2.x, self.h * anchor2.y))

    def set_callback(self, f):
        assert callable(f)
        self.change_callback = f
        if self.change_callback is not None:
            self.change_callback(self.k)

    def draw(self, window):
        x, y, w, h = self.get_rect(window)
        k = self.k
        assert 0 <= k <= 1
        pygame.draw.rect(window.screen, self.color, (x, y, w, h), 1)
        l = int(w * k)
        pygame.draw.rect(window.screen, self.color, (x, y - h + l, w, l), 0)

    def down(self, window, e):  # mouse down on it
        rect = self.get_rect(window)
        if rect.collidepoint(e.pos):
            print("bar in")
            window.mouse_up_callback.append(self.motion_end_callback)
            window.mouse_move_callback.append(self.motion_callback)
            return True

    def motion(self, window, e):
        rect = self.get_rect(window)
        os = window.os
        top, bottom = rect.top, rect.bottom
        x, y = e.pos
        if y < top:
            self.k = 1
        elif y > bottom:
            self.k = 0
        else:
            self.k = (bottom - y) / os(self.h)
        if self.change_callback is not None:
            self.change_callback(self.k)

    def motion_end(self, window, e):
        print("bar motion end")
        window.mouse_up_callback.remove(self.motion_end_callback)
        window.mouse_move_callback.remove(self.motion_callback)

    def reg_event(self, window):
        window.mouse_down_callback.append(self.down_callback)


class Barh(Control):  # 使用数学坐标       # 水平
    def __init__(self, p, k, color=(255, 255, 255), anchor=V2(-1, 0)):
        print("hi")
        self.k = k
        self.w = 0.8
        self.h = 0.2
        self.p = p
        self.color = color
        self.anchor = anchor

        self.draw_callback = self.draw
        self.down_callback = self.down
        self.motion_end_callback = self.motion_end
        self.motion_callback = self.motion
        self.change_callback = None

    def get_rect(self, window):
        os = window.os
        p = self.p_pixel(window)
        h = os(self.h)
        w = os(self.w)
        return Rect(p.x, p.y, w, h)

    p_pixel = Barv.p_pixel

    set_callback = Barv.set_callback

    def draw(self, window):
        x, y, w, h = self.get_rect(window)
        k = self.k
        assert 0 <= k <= 1
        pygame.draw.rect(window.screen, self.color, (x, y, w, h), 1)
        l = int(w * k)
        pygame.draw.rect(window.screen, self.color, (x, y, l, h), 0)

    down = Barv.down

    def motion(self, window, e):
        rect = self.get_rect(window)
        left, right = rect.left, rect.right
        x, y = e.pos
        if x > right:
            self.k = 1
        elif x < left:
            self.k = 0
        else:
            self.k = (x - left) / window.os(self.w)
        if self.change_callback is not None:
            self.change_callback(self.k)

    motion_end = Barv.motion_end

    reg_event = Barv.reg_event

class Wheelh(Control):  # 使用数学坐标       # 水平
    def __init__(self, p, color=(255, 255, 255), anchor=V2(0, 0)):
        self.w = 0.8        # 滚动一屏总变化为1
        self.h = 0.2
        self.p = p
        self.color = color
        self.anchor = anchor


        self.draw_callback = self.draw
        self.down_callback = self.down
        self.motion_end_callback = self.motion_end
        self.motion_callback = self.motion
        self.change_callback = None

    get_rect = Barv.get_rect

    p_pixel = Barv.p_pixel

    def set_callback(self, f):
        assert callable(f)
        self.change_callback = f

    def draw(self, window):
        x, y = self.p_pixel(window)
        os = window.os
        w = os(self.w)
        h = os(self.h)
        pygame.draw.rect(window.screen, self.color, self.get_rect(window), 1)

    def down(self, window, e):  # mouse down on it
        rect = self.get_rect(window)
        if rect.collidepoint(e.pos):
            print("wheel in")
            window.mouse_up_callback.append(self.motion_end_callback)
            window.mouse_move_callback.append(self.motion_callback)
            return True

    def motion(self, window, e):
        rect = self.get_rect(window)
        dx, dy = e.rel
        if self.change_callback is not None:
            self.change_callback(dx / window.os(self.w))

    def motion_end(self, window, e):
        print("wheel motion end")
        window.mouse_up_callback.remove(self.motion_end_callback)
        window.mouse_move_callback.remove(self.motion_callback)

    reg_event = Barv.reg_event


class Wheelv(Control):  # 使用数学坐标       # 水平
    def __init__(self, p, color=(255, 255, 255), anchor=V2(0, 0)):
        self.w = 0.2        # 滚动一屏总变化为1
        self.h = 0.8
        self.p = p
        self.color = color
        self.anchor = anchor


        self.draw_callback = self.draw
        self.down_callback = self.down
        self.motion_end_callback = self.motion_end
        self.motion_callback = self.motion
        self.change_callback = None

    get_rect = Wheelh.get_rect

    p_pixel = Wheelh.p_pixel

    set_callback = Wheelh.set_callback

    draw = Wheelh.draw

    down = Wheelh.down

    def motion(self, window, e):
        rect = self.get_rect(window)
        dx, dy = e.rel
        if self.change_callback is not None:
            self.change_callback(-dy / os(self.h))

    motion_end = Wheelh.motion_end

    reg_event = Barv.reg_event



class Wheelp(Control):   # wheel pie
    def __init__(self, p, r, color=(255, 255, 255), anchor=V2(0, 0)):
        self.p = p
        self.r = r
        self.color = color
        self.anchor = anchor

        self.draw_callback = self.draw
        self.down_callback = self.down
        self.motion_end_callback = self.motion_end
        self.motion_callback = self.motion
        self.change_callback = None

    def p_pixel(self, window):
        return window.o(self.p - self.r * self.anchor)
        o = window.o
        return o(self.p)

    set_callback = Wheelh.set_callback

    def draw(self, window):
        pygame.draw.circle(window.screen, self.color, self.p_pixel(window), window.os(self.r), 1)

    def down(self, window, e):  # mouse down on it
        os = window.os
        if (V2(e.pos) - self.p_pixel(window)).length() <= os(self.r):
            print("wheel in")
            window.mouse_up_callback.append(self.motion_end_callback)
            window.mouse_move_callback.append(self.motion_callback)
            return True

    def motion(self, window, e):
        dx, dy = e.rel
        os = window.os
        if self.change_callback is not None:
            self.change_callback(V2(dx, -dy) / os(self.r))

    def motion_end(self, window, e):
        print("wheel motion end")
        window.mouse_up_callback.remove(self.motion_end_callback)
        window.mouse_move_callback.remove(self.motion_callback)

    def reg_event(self, window):
        window.mouse_down_callback.append(self.down_callback)

class Label(Control):  # 使用数学坐标
    def __init__(self, p, s, color=(255, 255, 255)):
        self.p = p
        self.s = s
        self.color = color
        self.draw_callback = self.draw

    def p_pixel(self, window):
        return window.o(self.p)

    def draw(self, window):
        text = font1.render(self.s, True, self.color)
        # ~ window.screen.blit(text, self.p_pixel(window))
        window.screen.blit(text, self.p_pixel(window) + V2(0, -text.get_size()[1] // 2))


class Cup(Control):  # 可以移动的一个点, 使用数学坐标
    def __init__(self, p):
        self.p = p
        self.draw_callback = self.draw
        self.down_callback = self.down
        self.motion_end_callback = self.motion_end
        self.motion_callback = self.motion
        self.change_callback = None

    def p_pixel(self, window):
        return window.o(self.p)

    def get_rect(self, window):
        r = 5  # 边长是10像素
        return Rect(self.p_pixel(window) - V2(r, r), (2 * r, 2 * r))

    def set_callback(self, f):
        assert callable(f)
        self.change_callback = f
        if self.change_callback is not None:
            self.change_callback(self.k)

    def draw(self, window):
        pygame.draw.rect(window.screen, (255, 255, 255), self.get_rect(window), 1)

    def down(self, window, e):  # mouse down on it
        rect = self.get_rect(window)
        if rect.collidepoint(e.pos):
            print("cup in")
            window.mouse_up_callback.append(self.motion_end_callback)
            window.mouse_move_callback.append(self.motion_callback)
            return True

    def motion(self, window, e):
        p = window.o_inv(e.pos)
        self.p = p
        if self.change_callback is not None:
            self.change_callback(self)

    def motion_end(self, window, e):
        print("cup motion end")
        window.mouse_up_callback.remove(self.motion_end_callback)
        window.mouse_move_callback.remove(self.motion_callback)

    reg_event = Barv.reg_event



def pair(cls):
    p = cls.__dict__.get("p", V2(0, 0))
    k = cls.__dict__.get("k", 0)
    color = cls.__dict__.get("color", (255, 255, 255))
    f = cls.__dict__.get("f", lambda x: None)
    t = cls.__dict__.get("t", lambda x: x)  # 区间换算
    bar = Barh(p, k, color)
    label = Label(p + V2(0.9, 0), "{:.2f}".format(t(k)), color)
    def f2(x):
        f(t(x))
        label.s = "{:.2f}".format(t(x))
    bar.set_callback(f2)
    return bar, label

def join(window):
    def f(x):
        if hasattr(x, "__iter__"):
            for a in x:
                try:
                    a >> window
                except Exception as e:
                    print(x, ">> error")
                    raise e
        else:
            try:
                x >> window
            except Exception as e:
                print(x, ">> error")
                raise e
    return f

def grid(x, y):
    return V2(x * 1.5, y * 0.5)


class Walker(Control):
    def __init__(self, camera):
        self.camera = camera
        self.v = 3
        self.dirx = 0
        self.diry = 0
        self.dirz = 0

    def start(self, window, e):
        if e.key == pygame.K_TAB:
            window.key_down_callback.remove(self.start)
            window.key_down_callback.append(self.end)
            window.mouse_move_callback.append(self.rotate)
            window.key_down_callback.append(self.speed_change1)
            window.key_up_callback.append(self.speed_change2)
            window.update_callback.append(self.move_update)
            pygame.event.set_grab(True)
            pygame.mouse.set_visible(False)
            return True

    def end(self, window, e):
        if e.key == pygame.K_TAB:
            window.key_down_callback.remove(self.end)
            window.key_down_callback.append(self.start)
            window.mouse_move_callback.remove(self.rotate)
            window.key_down_callback.remove(self.speed_change1)
            window.key_up_callback.remove(self.speed_change2)
            window.update_callback.remove(self.move_update)
            pygame.event.set_grab(False)
            pygame.mouse.set_visible(True)
            return True

    def rotate(self, window, e):

        dx, dy = e.rel
        os = window.os
        dx, dy = dx / os(1), dy / os(1)
        k = 0.2
        th1, th2 = dx * k, dy * k
        self.camera.rot = vrot(U3.up, - th1) * self.camera.rot    #x axis
        self.camera.rot =  self.camera.rot * vrot(U3.rt, -th2)      # y axis
        midx, midy = window.o((0, 0))


    def speed_change1(self, window, e):
        if e.key == pygame.K_a:
            self.dirx = -1
            return True
        elif e.key == pygame.K_d:
            self.dirx = 1
            return True
        elif e.key == pygame.K_w:
            self.diry = 1
            return True
        elif e.key == pygame.K_s:
            self.diry = -1
            return True
        elif e.key == pygame.K_SPACE:
            self.dirz = 1
            return True
        elif e.key == pygame.K_LSHIFT:
            self.dirz = -1
            return True

    def speed_change2(self, window, e):
        if e.key == pygame.K_a or e.key == pygame.K_d:
            self.dirx = 0
            return True
        elif e.key == pygame.K_w or e.key == pygame.K_s:
            self.diry = 0
            return True
        elif e.key == pygame.K_SPACE or e.key == pygame.K_LSHIFT:
            self.dirz = 0
            return True

    def move_update(self, window, dt):
        v =  self.dirx * self.camera.right() + self.diry * (U3.up.cross(self.camera.right())) + \
            U3(0, 0, self.dirz)
        if v.length > 0:
            v = v.normalized
            self.camera.pos += v * self.v * dt



    def reg_event(self, window):
        window.key_down_callback.append(self.start)



